﻿#include "stdafx.h"
#if _QG_USE_GL
#include "qg_ogl_dev.h"
#include "qg_ogl_dev_ext.h"
#include "qg_ogl_mtrl.h"
#include "qg_ogl_cnv.h"
#include "qg_trace.h"

#ifndef GL_VERSION_2_0
#error need OPENGL 2.0 or higher version header.
#endif

using namespace _ogl;

//
bool GLDevice::_BindShader() throw()
{
	if (_sprn->bd_shd)
	{
		_sprn->bd_shd = false;
		if (!_CommitShader())
			return false;
	}

	if (_sprn->m_shd)
	{
		GLProgram* p = (GLProgram*)_sprn->m_shd;
		p->_ProcessMap();
	}

	return true;
}

//
bool GLDevice::_CommitShader() throw()
{
	if (!_sprn->m_shd)
		QglUseProgram(0);
	else
	{
		GLProgram* p = (GLProgram*)_sprn->m_shd;

		if (!p->_linked)
		{
			// 링크 안된 넘이다
			kobj_tracet(KLV_WARNING, KERR_NOREF, "no linked shader specified. will be linking instead.");
			kobj_tracef(KLV_WARNING, "target shader is '%s'", p->_key.Data());

			if (!p->Link())
				return false;
		}

		GLuint u = (GLuint)p->GetDescription();
		QglUseProgram(u);
	}

	return true;
}

//
qgShader* GLDevice::_RoamImplShader(const char* name)
{
	GLProgram* shd = kobj_create(GLProgram);

	if (name)
		shd->_key = name;
	else
	{
		ksize_t n = k_nth();
		shd->_key.Format("gl_program_%Lu", n);
	}

	return shd;
}


//////////////////////////////////////////////////////////////////////////
// 세이더 프로그램
KIMPL_SUBCLASS(GLProgram, "GLProgram");

//
void GLProgram::TypeInitialize(kType type) throw()
{
	// 해시를 위해서
	_InitAutoFunc();
}

//
GLProgram::GLProgram() throw()
{
	// 설마 0은 안나오겠지...
	_node_desc = (kuintptr)QglCreateProgram();
}

//
GLProgram::~GLProgram() throw()
{
	_RemoveHandle(_rfp, 0);
	_RemoveHandle(_rvp, 0);

	QglDeleteProgram((GLuint)_node_desc);
}

//
void GLProgram::_PrintError(GLuint handle, kint errcode, const char* target, bool shdinfo, bool prginfo) throw()
{
	kobj_tracef(KLV_ERROR, "%s with program '%s'", k_errmsg(errcode), target);

	if (shdinfo)
	{
		GLint maxlen = 0;
		QglGetShaderiv(handle, GL_INFO_LOG_LENGTH, &maxlen);

		if (maxlen > 0)
		{
			GLint len;
			GLchar* psz = (GLchar*)alloca(maxlen*sizeof(GLchar));
			QglGetShaderInfoLog(handle, maxlen, &len, psz);

			kobj_tracef(KLV_ERROR, "shader info %s", psz);
		}
	}

	if (prginfo)
	{
		GLint maxlen = 0;
		QglGetProgramiv(handle, GL_INFO_LOG_LENGTH, &maxlen);

		if (maxlen > 0)
		{
			GLint len;
			GLchar* psz = (GLchar*)alloca(maxlen*sizeof(GLchar));
			QglGetProgramInfoLog(handle, maxlen, &len, psz);

			kobj_tracef(KLV_ERROR, "program info %s", psz);
		}
	}
}

//
GLProgram::RefHandle* GLProgram::_CompileProgram(qgShaderType type, const char* src, GLint len) throw()
{
	// GLES2 -> http://msdn.microsoft.com/ko-kr/library/windows/apps/dn166905.aspx
	// OPENGL 2.0도 비슷하다.

	GLenum gltype = GLConvert::ShaderType(type);
	GLuint sh = QglCreateShader(gltype);

	if (!sh)
		return NULL;

	//
	QglShaderSource(sh, 1, &src, &len);
	QglCompileShader(sh);

	GLint status = 0;
	QglGetShaderiv(sh, GL_COMPILE_STATUS, &status);

	if (!status)
	{
		_PrintError(sh, KERR_PARSE, qgResSupp::ShaderTypeToString(type), true, false);
		return NULL;
	}

	return new RefHandle(sh);
}

//
bool GLProgram::Link()
{
	k_return_value_if_ok(_linked, true);

	if (!_rfp && !_rvp)
	{
		// 참조할 세이더가 모두 없다
		kobj_tracep(KLV_ERROR, KERR_ARGUMENT);
		return false;
	}

	//
	GLuint sh = (GLuint)_node_desc;

	QglLinkProgram(sh);

	GLint status = 0;
	QglGetProgramiv(sh, GL_LINK_STATUS, &status);

	if (!status)
	{
		_PrintError(sh, KERR_PARSE, "Link", false, true);
		return false;
	}

	// 여기서 분석한다
	GLint cnt = 0, maxlen = 0;
	char sz[64];

	// 유니폼 -> 전역 변수
	QglGetProgramiv(sh, GL_ACTIVE_UNIFORMS, &cnt);

	if (cnt > 0)
	{
		QglGetProgramiv(sh, GL_ACTIVE_UNIFORM_MAX_LENGTH, &maxlen);

		if (maxlen == 0 || maxlen > 31)
		{
			// 굳이 오류는 아님.
			kobj_tracet(KLV_WARNING, KERR_PROPERTY, "invalid maximum length of uniforms.");
		}

		//
		_vars.Resize(cnt);

		for (GLint i = 0; i < cnt; i++)
		{
			GLint glsize;
			GLenum gltype;
			QglGetActiveUniform(sh, i, 32 - 1, NULL, &glsize, &gltype, sz);

			qgShaderData datatype = _GlTypeToShaderData(gltype);
			if (datatype == QGSHDD_UNKNOWN)
			{
				kobj_tracet(KLV_WARNING, KERR_SYNTAX, "no support shader uniform data type.");
				kobj_tracef(KLV_WARNING, "type id '%X' of shader '%s'", gltype, _key.Data());
			}

			qgShaderVar& v = _vars[i];
			v.name = sz;
			v.hash = v.name.Hash(true);
			v.offset = QglGetUniformLocation(sh, sz);
			v.size = (kint)glsize;
			v.data = datatype;
			_MatchAutoFunc(v);
			v.ptr = NULL; // baad 데이터가 있으면 좀 그러니깐 초기화
		}
	}

	// 속성 -> 인수
	QglGetProgramiv(sh, GL_ACTIVE_ATTRIBUTES, &cnt);

	if (cnt > 0)
	{
		QglGetProgramiv(sh, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &maxlen);

		if (maxlen == 0)
		{
			// 굳이 오류는 아님.
			//kobj_tracet(KLV_WARNING, KERR_PROPERTY, "invalid maximum length of attributes.");

			// 이 경우는 드라이버가 gl_로 시작하는 애들을 attribute로 처리했을 경우에다,
			// 사용자가 지정한 attribute가 하나도 없을 때를 의미함.
			//
			// 고정 기능과 호환되는 기능을 사용하기 때문에 attribute로 묶을 필요는 없다
			// 어짜피 glGetAttribLocation() 함수가 -1을 반환하기 때문에 쓸 수도 없다
		}

		//
		_atrs.Resize(cnt);

		for (kint i = 0; i < QGDLOU_MAX_VALUE; i++)
		{
			_acnt[i] = 0;
			_alnk[i] = NULL;
		}

		for (GLint i = 0; i < cnt; i++)
		{
			GLint glsize;
			GLenum gltype;
			QglGetActiveAttrib(sh, i, 32 - 1, NULL, &glsize, &gltype, sz);

			qgShaderData datatype = _GlTypeToShaderData(gltype);
			if (datatype == QGSHDD_UNKNOWN)
			{
				kobj_tracet(KLV_WARNING, KERR_SYNTAX, "no support shader attribute data type.");
				kobj_tracef(KLV_WARNING, "type id '%X' of shader '%s'", gltype, _key.Data());
			}

			AttribVar& a = _atrs[i];
			a.name = sz;
			a.hash = a.name.Hash(true);
			a.data = datatype;
			a.size = (kuint)glsize;
			a.offset = QglGetAttribLocation(sh, sz);

			if (a.offset < 0)
			{
				a.usage = (qgDloUsage)0xFF;
				a.index = -1;
				a.next = NULL;
			}
			else
			{
				a.usage = _AttribNameToLayoutUsage(sz);

				if (a.usage >= QGDLOU_MAX_VALUE)
				{
					a.index = 0;
					a.next = NULL;
				}
				else
				{
					a.index = _acnt[a.usage];
					_acnt[a.usage]++;

					// 인덱스 검사를 위해 저장한다. 단, 저장 순서는 거꾸로라는거 주의
					a.next = _alnk[a.usage];
					_alnk[a.usage] = &a;
				}
			}
		}
	}

	//
	_linked = true;

	return true;
}

//
bool GLProgram::_InternalBindData(qgShaderType type, kconstpointer data, kint size, kint flags) throw()
{
	// 매크로 패스
	// 인클루드 패스
	// 플래그 패스
	// 킵 없음

	switch (K_CAST_ENUM(type))
	{
		case QGSHADER_PS:
			_RemoveHandle(_rfp, (GLuint)_node_desc);
			if ((_rfp = _CompileProgram(QGSHADER_PS, (const char*)data, (GLint)size)) == NULL)
			{
				K_UFLAG(&_insts, QGSHDI_PS, false);
				return false;
			}
			else
			{
				QglAttachShader((GLuint)_node_desc, _rfp->handle);
				K_UFLAG(&_insts, QGSHDI_PS, true);
			}
			break;

		case QGSHADER_VS:
			_RemoveHandle(_rvp, (GLuint)_node_desc);
			if ((_rvp = _CompileProgram(QGSHADER_VS, (const char*)data, (GLint)size)) == NULL)
			{
				K_UFLAG(&_insts, QGSHDI_VS, false);
				return false;
			}
			else
			{
				QglAttachShader((GLuint)_node_desc, _rvp->handle);
				K_UFLAG(&_insts, QGSHDI_VS, true);
			}
			break;

		default:
			return false;
	}

	_linked = false;

	return true;
}

//
bool GLProgram::BindFile(qgShaderType type, const char* filename, kint flags) throw()
{
	kint readsize;
	tpl::ScopedMemPtrAlloc<kpointer> readdata(_dev->FileRead(filename, &readsize));

	k_return_value_if_fail(readdata, false);

	return _InternalBindData(type, readdata, readsize, flags);
}

//
bool GLProgram::BindData(qgShaderType type, kconstpointer data, kint size, kint flags) throw()
{
	k_return_value_if_fail(data && size > 0, false);

	return _InternalBindData(type, data, size, flags);
}

//
bool GLProgram::BindShader(qgShaderType type, const char* name) throw()
{
	k_return_value_if_fail(name, false);

	// 찾는다
	qgShader::KeyType key(name);
	GLProgram* p = (GLProgram*)((GLDevice*)_dev)->_RoamFind(QGROAM_MTL_2, &key);
	k_return_value_if_fail(p, false);

	// 찾는게 있으면 세이더를 참조해둔다
	switch (K_CAST_ENUM(type))
	{
		case QGSHADER_PS:
			_RemoveHandle(_rfp, (GLuint)_node_desc);
			if (!p->_rfp)
			{
				_rfp = NULL;
				K_UFLAG(&_insts, QGSHDI_PS, false);
				return false;
			}
			else
			{
				_rfp = p->_rfp;
				k_atominc(&_rfp->ref);
				QglAttachShader((GLuint)_node_desc, _rfp->handle);
				K_UFLAG(&_insts, QGSHDI_PS, true);
			}
			break;

		case QGSHADER_VS:
			_RemoveHandle(_rvp, (GLuint)_node_desc);
			if (!p->_rvp)
			{
				_rvp = NULL;
				K_UFLAG(&_insts, QGSHDI_VS, false);
				return false;
			}
			else
			{
				_rvp = p->_rvp;
				k_atominc(&_rvp->ref);
				QglAttachShader((GLuint)_node_desc, _rvp->handle);
				K_UFLAG(&_insts, QGSHDI_VS, true);
			}
			break;

		default:
			return false;
	}

	_linked = false;

	return true;
}

//
qgShaderVar* GLProgram::GetVars(kint stage, kint* count)
{
	if (stage > 0)
	{
		if (count)
			*count = 0;
		return NULL;
	}
	else
	{
		if (count)
			*count = _vars.Count();
		return _vars.Data();
	}
}

//
GLProgram::AttribVar* GLProgram::FindAttribUsage(qgDloUsage usage, kint index) throw()
{
#if 0
	// 이 루틴... 리얼타임으로 불릴텐데 느릴 것 같다.
	kForEach(AttribVar& a, _atrs)
	{
		if (usage == a.usage && index == a.index)
			return &a;
	}

	return NULL;
#else
	AttribVar* v = _alnk[usage];
	for (kint i = 0; i < index; i++)
	{
		if (!v)
			break;
		v = v->next;
	}
	return v;
#endif
}

//
GLint GLProgram::FindFixedAttribUsage(qgDloUsage usage, kint index) throw()
{
	// OgreGLGpuProgram.cpp 에서 가져옴
	// GLuint GLGpuProgram::getFixedAttributeIndex(VertexElementSemantic semantic, uint index)

	// Some drivers (e.g. OS X on nvidia) incorrectly determine the attribute binding automatically
	// and end up aliasing existing built-ins. So avoid! Fixed builtins are: 

	//  a  builtin				custom attrib name
	// ----------------------------------------------
	//	0  gl_Vertex			vertex				-> position
	//  1  n/a					blendWeights		-> weight
	//	2  gl_Normal			normal				-> normal
	//	3  gl_Color				colour				-> color[0]
	//	4  gl_SecondaryColor	secondary_colour	-> color[1]
	//	5  gl_FogCoord			fog_coord			-> 없음
	//  7  n/a					blendIndices		->index
	//	8  gl_MultiTexCoord0	uv0
	//	9  gl_MultiTexCoord1	uv1
	//	10 gl_MultiTexCoord2	uv2
	//	11 gl_MultiTexCoord3	uv3
	//	12 gl_MultiTexCoord4	uv4
	//	13 gl_MultiTexCoord5	uv5
	//	14 gl_MultiTexCoord6	uv6, tangent		-> tangent
	//	15 gl_MultiTexCoord7	uv7, binormal		-> binormal
	switch (K_CAST_ENUM(usage))
	{
		case QGDLOU_POSITION:
			return 0;

		case QGDLOU_COLOR:
			return index == 0 ? 3 : 4;

		case QGDLOU_WEIGHT:
			return 1;

		case QGDLOU_INDEX:
			return 7;

		case QGDLOU_NORMAL:
			return 2;

		case QGDLOU_TEXTURE:
			return 8 + index;

		case QGDLOU_TANGENT:
			return 14;

		case QGDLOU_BINORMAL:
			return 15;
	};

	return 0xFFFF;
}

//
void GLProgram::_RemoveHandle(RefHandle* rh, GLuint program) throw()
{
	if (rh)
	{
		if (program > 0)
			QglDetachShader(program, rh->handle);

		if (k_atomdec(&rh->ref) == 0)
		{
			QglDeleteShader(rh->handle);

			delete rh;
		}
	}
}

//
qgShaderData GLProgram::_GlTypeToShaderData(GLenum type) throw()
{
	switch (K_CAST_ENUM(type))
	{
		case GL_FLOAT:
			return QGSHDD_FLOAT1;

		case GL_FLOAT_VEC2:
			return QGSHDD_FLOAT2;

		case GL_FLOAT_VEC3:
			return QGSHDD_FLOAT3;

		case GL_FLOAT_VEC4:
			return QGSHDD_FLOAT4;

		case GL_FLOAT_MAT4:
			return QGSHDD_FLOAT16;

		case GL_SAMPLER_1D:
			return QGSHDD_INT1;

		case GL_SAMPLER_2D:
			return QGSHDD_INT1;

		case GL_SAMPLER_3D:
			return QGSHDD_INT1;

		case GL_SAMPLER_CUBE:
			return QGSHDD_INT1;

		case GL_SAMPLER_1D_SHADOW:
			return QGSHDD_INT1;

		case GL_SAMPLER_2D_SHADOW:
			return QGSHDD_INT1;

		case GL_INT:
			return QGSHDD_INT1;

		case GL_INT_VEC2:
			return QGSHDD_INT2;

		case GL_INT_VEC3:
			return QGSHDD_INT3;

		case GL_INT_VEC4:
			return QGSHDD_INT4;

		case GL_BOOL:
			return QGSHDD_BYTE1;

		case GL_BOOL_VEC2:
			return QGSHDD_BYTE2;

		case GL_BOOL_VEC3:
			return QGSHDD_BYTE3;

		case GL_BOOL_VEC4:
			return QGSHDD_BYTE4;
	}

	return QGSHDD_UNKNOWN;
}

//
qgDloUsage GLProgram::_AttribNameToLayoutUsage(const char* name) throw()
{
	// 각 속성 이름에 "+숫자" 일 경우가 있기 때문에 길이 비교를 한다
	// 예를 들면 색깔은 acolor 또는 acolor0, 색깔2(스페큘러)는 acolor1 이런 식

	if (k_strnicmp(name, "aposition", 9) == 0)
		return QGDLOU_POSITION;

	if (k_strnicmp(name, "acolor", 6) == 0)
		return QGDLOU_COLOR;

	if (k_strnicmp(name, "aweight", 7) == 0)
		return QGDLOU_WEIGHT;

	if (k_strnicmp(name, "aindex", 6) == 0)
		return QGDLOU_INDEX;

	if (k_strnicmp(name, "anormal", 7) == 0)
		return QGDLOU_NORMAL;

	if (k_strnicmp(name, "atexcoord", 9) == 0)
		return QGDLOU_TEXTURE;

	if (k_strnicmp(name, "atangent", 8) == 0)
		return QGDLOU_TANGENT;

	if (k_strnicmp(name, "abinormal", 9) == 0)
		return QGDLOU_BINORMAL;

	return (qgDloUsage)0xFF;
}

//
void GLProgram::_ProcessMap() throw()
{
	kForEach(qgShaderVar& v, _vars)
	{
		// OPENGL은 data에 offset을 넣는다. 안넣어도 되고
		if (v.role == QGSHDR_AUTO)
			((qgShaderAutoFunc)v.intr)(_dev, K_CAST_INT_TO_PTR(v.offset), v);
	}

	// 사용자 지정
	if (_cbintr)
		_cbintr(_vars.Count(), _vars.Data(), this);
}


//////////////////////////////////////////////////////////////////////////
// 자동 세이더 변수 처리
class GLProgramWorker
{
	// 얘도 friend 떄문에 namespace가 아니고 class임.

private:
	static void OrthoProj(GLDevice* dev, kpointer data, const qgShaderVar& v)
	{
		k_assert_eq(v.data, QGSHDD_FLOAT16);
		k_assert_eq(v.size, 1);
		QglUniformMatrix4fv(v.offset, 1, false, dev->_trfm.otm.ToPointer());
	}

	static void World(GLDevice* dev, kpointer data, const qgShaderVar& v)
	{
		k_assert_eq(v.data, QGSHDD_FLOAT16);
		k_assert_eq(v.size, 1);
		QglUniformMatrix4fv(v.offset, 1, false, dev->_trfm.tm[QGTMS_WORLD].ToPointer());
	}

	static void View(GLDevice* dev, kpointer data, const qgShaderVar& v)
	{
		k_assert_eq(v.data, QGSHDD_FLOAT16);
		k_assert_eq(v.size, 1);
		QglUniformMatrix4fv(v.offset, 1, false, dev->_trfm.tm[QGTMS_VIEW].ToPointer());
	}

	static void Proj(GLDevice* dev, kpointer data, const qgShaderVar& v)
	{
		k_assert_eq(v.data, QGSHDD_FLOAT16);
		k_assert_eq(v.size, 1);
		QglUniformMatrix4fv(v.offset, 1, false, dev->_trfm.tm[QGTMS_PROJ].ToPointer());
	}

	static void ViewProj(GLDevice* dev, kpointer data, const qgShaderVar& v)
	{
		k_assert_eq(v.data, QGSHDD_FLOAT16);
		k_assert_eq(v.size, 1);
		QglUniformMatrix4fv(v.offset, 1, false, dev->_trfm.vipr.ToPointer());
	}

	static void InvView(GLDevice* dev, kpointer data, const qgShaderVar& v)
	{
		k_assert_eq(v.data, QGSHDD_FLOAT16);
		k_assert_eq(v.size, 1);
		QglUniformMatrix4fv(v.offset, 1, false, dev->_trfm.invv.ToPointer());
	}

	static void WorldViewProj(GLDevice* dev, kpointer data, const qgShaderVar& v)
	{
		k_assert_eq(v.data, QGSHDD_FLOAT16);
		k_assert_eq(v.size, 1);
		QglUniformMatrix4fv(v.offset, 1, false, (dev->_trfm.tm[QGTMS_WORLD] * dev->_trfm.vipr).ToPointer());
	}

	static void Tex0(GLDevice* dev, kpointer data, const qgShaderVar& v)
	{
	}

	static void Tex1(GLDevice* dev, kpointer data, const qgShaderVar& v)
	{
	}

	static void Tex2(GLDevice* dev, kpointer data, const qgShaderVar& v)
	{
	}

	static void Tex3(GLDevice* dev, kpointer data, const qgShaderVar& v)
	{
	}

public:
	static qgShaderAutoInfo Autos[QGSHDA_MAX_VALUE];
};

// 목록
#define SV(m,t)	{0, "s" K_STRING(m), t, (qgShaderAutoFunc)&GLProgramWorker::m}
qgShaderAutoInfo GLProgramWorker::Autos[] =
{
	SV(OrthoProj, QGSHDA_ORTHO_PROJ),
	SV(World, QGSHDA_WORLD),
	SV(View, QGSHDA_VIEW),
	SV(Proj, QGSHDA_PROJ),
	SV(ViewProj, QGSHDA_VIEWPROJ),
	SV(InvView, QGSHDA_INVVIEW),
	SV(WorldViewProj, QGSHDA_WORLDVIEWPROJ),
	SV(Tex0, QGSHDA_TEX0),
	SV(Tex1, QGSHDA_TEX1),
	SV(Tex2, QGSHDA_TEX2),
	SV(Tex3, QGSHDA_TEX3),
};
#undef SV

//
void GLProgram::_InitAutoFunc() throw()
{
	for (ksize_t i = 0; i < K_COUNTOF(GLProgramWorker::Autos); i++)
		GLProgramWorker::Autos[i].hash = k_strihash(GLProgramWorker::Autos[i].name);
}

//
kpointer GLProgram::_MatchAutoFunc(qgShaderVar& v) throw()
{
	for (ksize_t i = 0; i < K_COUNTOF(GLProgramWorker::Autos); i++)
	{
		if (v.hash == GLProgramWorker::Autos[i].hash &&
			v.name.CompareTo(GLProgramWorker::Autos[i].name, true) == 0)
		{
			// 찾음
			v.role = QGSHDR_AUTO;
			v.intr = (kpointer)GLProgramWorker::Autos[i].func;

			return NULL;
		}
	}

	// 없음
	v.role = QGSHDR_MANUAL;
	v.intr = NULL;

	return NULL;
}

#endif	// _QG_USE_GL
